/*
  Copyright (c) 1999 Rafal Wojtczuk <nergal@7bulls.com>. All rights reserved.
  See the file COPYING for license details.
 */

#include <config.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/ip_icmp.h>

#include "checksum.h"
#include "scan.h"
#include "tcp.h"
#include "util.h"
#include "nids.h"
#include "hash.h"
#include "tcp_conn_pool.h"

#if ! HAVE_TCP_STATES
enum {
  TCP_ESTABLISHED = 1,
  TCP_SYN_SENT,
  TCP_SYN_RECV,
  TCP_FIN_WAIT1,
  TCP_FIN_WAIT2,
  TCP_TIME_WAIT,
  TCP_CLOSE,
  TCP_CLOSE_WAIT,
  TCP_LAST_ACK,
  TCP_LISTEN,
  TCP_CLOSING			/* now a valid state */
};

#endif

#define FIN_SENT 120
#define FIN_CONFIRMED 121
#define COLLECT_cc 1
#define COLLECT_sc 2
#define COLLECT_ccu 4
#define COLLECT_scu 8

#define EXP_SEQ (snd->first_data_seq + rcv->count + rcv->urg_count)

extern struct proc_node *tcp_procs;
#ifdef ENABLE_TCPREASM
extern struct proc_node *tcp_resume_procs;
#endif

/* static struct tcp_stream **tcp_stream_table; */
/* static struct tcp_stream *streams_pool; */
/* static int tcp_num = 0; */
/* static int tcp_stream_table_size; */
/* static int max_stream; */
/* static struct tcp_stream *tcp_latest = 0, *tcp_oldest = 0; */
/* static struct tcp_stream *free_streams; */


/*
 * cause  lvs will bring client ip in tcp option 254,
 * get the real client addr here
 */
void get_real_client_addr(struct tcp_stream *a_tcp, struct tcphdr *this_tcphdr) {

    struct tcp_opt_exp *opt_exp = NULL;
    int len = 4 * this_tcphdr->th_off;
    unsigned char * options = (unsigned char*)(this_tcphdr + 1);
    int ind = 0;
    while (ind <=  len - (int)sizeof (struct tcphdr) - 3 )
  	switch (options[ind]) {
		case 0: /* TCPOPT_EOL */
			return;
		case 1: /* TCPOPT_NOP */
			ind++;
			continue;	
        case 254:
          opt_exp = (struct tcp_opt_exp *)(&options[ind]);
          if(opt_exp->opcode == TCPOPT_EXP && opt_exp->opsize == TCPOPT_EXP_SIZE){
              a_tcp->addr.source = opt_exp->port;
              a_tcp->addr.saddr = opt_exp->ip;
          }
		default:	
			if (options[ind+1] < 2 ) /* "silly option" */
				return;
			ind += options[ind+1];
	}			

}
static struct ip *ugly_iphdr;
struct tcp_timeout *nids_tcp_timeouts = 0;

void purge_queue(struct half_stream * h)
{
  struct skbuff *tmp, *p = h->list;

  while (p) {
    free(p->data);
    tmp = p->next;
    free(p);
    p = tmp;
  }
  h->list = h->listtail = 0;
  h->rmem_alloc = 0;
}

void
add_tcp_closing_timeout(struct tcp_stream * a_tcp)
{
  struct tcp_timeout *to;
  struct tcp_timeout *newto;

  if (!nids_params.tcp_workarounds)
    return;
  newto = malloc(sizeof (struct tcp_timeout));
  if (!newto)
      nids_params.no_mem("add_tcp_closing_timeout");
  newto->a_tcp = a_tcp;
  newto->timeout.tv_sec = nids_last_pcap_header->ts.tv_sec + 10;
  newto->prev = 0;
  for (newto->next = to = nids_tcp_timeouts; to; newto->next = to = to->next) {
    if (to->a_tcp == a_tcp) {
      free(newto);
      return;
    }
    if (to->timeout.tv_sec > newto->timeout.tv_sec)
      break;
    newto->prev = to;
  }
  if (!newto->prev)
    nids_tcp_timeouts = newto;
  else
    newto->prev->next = newto;
  if (newto->next)
    newto->next->prev = newto;
}

void
del_tcp_closing_timeout(struct tcp_stream * a_tcp)
{
  struct tcp_timeout *to;

  if (!nids_params.tcp_workarounds)
    return;
  for (to = nids_tcp_timeouts; to; to = to->next)
    if (to->a_tcp == a_tcp)
      break;
  if (!to)
    return;
  if (!to->prev)
    nids_tcp_timeouts = to->next;
  else
    to->prev->next = to->next;
  if (to->next)
    to->next->prev = to->prev;
  free(to);
}

void
nids_free_tcp_stream(struct tcp_stream * a_tcp)
{
  /* int hash_index = a_tcp->hash_index; */
  /* struct lurker_node *i, *j; */

  /* del_tcp_closing_timeout(a_tcp); */
  /* purge_queue(&a_tcp->server); */
  /* purge_queue(&a_tcp->client); */
   
  /* if (a_tcp->next_node) */
  /*   a_tcp->next_node->prev_node = a_tcp->prev_node; */
  /* if (a_tcp->prev_node) */
  /*   a_tcp->prev_node->next_node = a_tcp->next_node; */
  /* else */
  /*   tcp_stream_table[hash_index] = a_tcp->next_node; */
  /* if (a_tcp->client.data) */
  /*   free(a_tcp->client.data); */
  /* if (a_tcp->server.data) */
  /*   free(a_tcp->server.data); */
  /* if (a_tcp->next_time) */
  /*   a_tcp->next_time->prev_time = a_tcp->prev_time; */
  /* if (a_tcp->prev_time) */
  /*   a_tcp->prev_time->next_time = a_tcp->next_time; */
  /* if (a_tcp == tcp_oldest) */
  /*   tcp_oldest = a_tcp->prev_time; */
  /* if (a_tcp == tcp_latest) */
  /*   tcp_latest = a_tcp->next_time; */
  
  /* i = a_tcp->listeners; */
  
  /* while (i) { */
  /*   j = i->next; */
  /*   free(i); */
  /*   i = j; */
  /* } */
  /* a_tcp->next_free = free_streams; */
  /* free_streams = a_tcp; */
  /* tcp_num--; */
    conn_pool_del(a_tcp);
}

void
tcp_check_timeouts(struct timeval *now)
{
  struct tcp_timeout *to;
  struct tcp_timeout *next;
  struct lurker_node *i;

  for (to = nids_tcp_timeouts; to; to = next) {
    if (now->tv_sec < to->timeout.tv_sec)
      return;
    to->a_tcp->nids_state = NIDS_TIMED_OUT;
    for (i = to->a_tcp->listeners; i; i = i->next)
      (i->item) (to->a_tcp, &i->data);
    next = to->next;
    nids_free_tcp_stream(to->a_tcp);
  }
}

/* static int */
/* mk_hash_index(struct tuple4 addr) */
/* { */
/*   int hash=mkhash(addr.saddr, addr.source, addr.daddr, addr.dest); */
/*   return hash % tcp_stream_table_size; */
/* } */

static int get_ts(struct tcphdr * this_tcphdr, unsigned int * ts)
{
  int len = 4 * this_tcphdr->th_off;
  unsigned int tmp_ts;
  unsigned char * options = (unsigned char*)(this_tcphdr + 1);
  int ind = 0, ret = 0;
  while (ind <=  len - (int)sizeof (struct tcphdr) - 10 )
  	switch (options[ind]) {
		case 0: /* TCPOPT_EOL */
			return ret;
		case 1: /* TCPOPT_NOP */
			ind++;
			continue;	
  		case 8: /* TCPOPT_TIMESTAMP */
	  		memcpy((char*)&tmp_ts, options + ind + 2, 4);
  			*ts=ntohl(tmp_ts);
			ret = 1;
			/* no break, intentionally */
		default:	
			if (options[ind+1] < 2 ) /* "silly option" */
				return ret;
			ind += options[ind+1];
	}			
			
  return ret;
}  		

static int get_wscale(struct tcphdr * this_tcphdr, unsigned int * ws)
{
  int len = 4 * this_tcphdr->th_off;
  unsigned int tmp_ws;
  unsigned char * options = (unsigned char*)(this_tcphdr + 1);
  int ind = 0, ret = 0;
  *ws=1;
  while (ind <=  len - (int)sizeof (struct tcphdr) - 3 )
  	switch (options[ind]) {
		case 0: /* TCPOPT_EOL */
			return ret;
		case 1: /* TCPOPT_NOP */
			ind++;
			continue;	
  		case 3: /* TCPOPT_WSCALE */
  			tmp_ws=options[ind+2];
  			if (tmp_ws>14) 
  				tmp_ws=14;
			*ws=1<<tmp_ws;
			ret = 1;
			/* no break, intentionally */
		default:	
			if (options[ind+1] < 2 ) /* "silly option" */
				return ret;
			ind += options[ind+1];
	}			
			
  return ret;
}  		

#ifdef ENABLE_TCPREASM
/* static struct tcp_stream * */
/* initiate_tcp_resume(struct tcphdr * this_tcphdr, struct ip * this_iphdr, int direction) */
/* { */
/* 	struct tcp_stream *tolink; */
/* 	struct tcp_stream *a_tcp; */
/* 	int hash_index; */
/* 	struct tuple4 addr; */
/* 	struct half_stream *half; */
/* 	struct half_stream *other_half; */
	
/* 	switch (direction) */
/* 	{ */
/* 		case NIDS_TCP_RESUME_CLIENT: */
/* 			addr.source = ntohs(this_tcphdr->th_sport); */
/* 			addr.dest = ntohs(this_tcphdr->th_dport); */
/* 			addr.saddr = this_iphdr->ip_src.s_addr; */
/* 			addr.daddr = this_iphdr->ip_dst.s_addr; */
/* 			break; */
/* 		case NIDS_TCP_RESUME_SERVER: */
/* 			addr.source = ntohs(this_tcphdr->th_dport); */
/* 			addr.dest = ntohs(this_tcphdr->th_sport); */
/* 			addr.saddr = this_iphdr->ip_dst.s_addr; */
/* 			addr.daddr = this_iphdr->ip_src.s_addr; */
/* 			break; */
/* 		default: */
/* 			return NULL; */
/* 	} */
/* 	hash_index = mk_hash_index(addr); */
	
/* 	if (tcp_num > max_stream) { */
/* 		struct lurker_node *i; */
		
/* 		tcp_oldest->nids_state = NIDS_TIMED_OUT; */
/* 		for (i = tcp_oldest->listeners; i; i = i->next) */
/* 			(i->item) (tcp_oldest, &i->data); */
/* 		nids_free_tcp_stream(tcp_oldest); */
/* 		nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_TOOMUCH, ugly_iphdr, this_tcphdr); */
/* 	} */
/* 	a_tcp = free_streams; */
/* 	if (!a_tcp) { */
/* 		fprintf(stderr, "gdb me ...\n"); */
/* 		pause(); */
/* 	} */
/* 	free_streams = a_tcp->next_free; */
	
/* 	tcp_num++; */
/* 	tolink = tcp_stream_table[hash_index]; */
/* 	memset(a_tcp, 0, sizeof(struct tcp_stream)); */
/* 	a_tcp->hash_index = hash_index; */
/* 	a_tcp->addr = addr; */
/* 	if (direction == NIDS_TCP_RESUME_CLIENT) */
/* 	{ */
/* 		half = &a_tcp->client; */
/* 		other_half = &a_tcp->server; */
/* 	} */
/* 	else */
/* 	{ */
/* 		half = &a_tcp->server; */
/* 		other_half = &a_tcp->client; */
/* 	} */
/* 	half->state = TCP_ESTABLISHED; */
/* 	half->seq = ntohl(this_tcphdr->th_seq) + 1; */
/* 	half->first_data_seq = half->seq - 1; */
/* 	half->window = ntohs(this_tcphdr->th_win); */
/* 	half->ts_on = 0; */
/* 	half->wscale = nids_params.tcp_resume_wscale; */
/* 	if (this_tcphdr->th_flags & TH_ACK) */
/* 		half->ack_seq = ntohl(this_tcphdr->th_ack); */
	
/* #ifdef ENABLE_TCPREASM_DEBUG */
/* 	DEBUG_REASSEMBLY("new connection: seq = %u, ack_seq = %u\n", */ 
/* 					 half->seq, half->ack_seq); */
/* #endif */
	
/* 	other_half->ack_seq = half->seq; */
/* 	other_half->state = TCP_ESTABLISHED; */
/* 	other_half->resume_second_half = 1; */
/* 	other_half->ts_on = 0; */
/* 	other_half->window = half->window; */
/* 	other_half->wscale = nids_params.tcp_resume_wscale; */
/* 	a_tcp->next_node = tolink; */
/* 	a_tcp->prev_node = 0; */
/* 	if (tolink) */
/* 		tolink->prev_node = a_tcp; */
/* 	tcp_stream_table[hash_index] = a_tcp; */
/* 	a_tcp->next_time = tcp_latest; */
/* 	a_tcp->prev_time = 0; */
/* 	if (!tcp_oldest) */
/* 		tcp_oldest = a_tcp; */
/* 	if (tcp_latest) */
/* 		tcp_latest->prev_time = a_tcp; */
/* 	tcp_latest = a_tcp; */
	
/* 	return a_tcp; */
/* } */

static struct tcp_stream *
initiate_tcp_resume(struct tcphdr * this_tcphdr, struct ip * this_iphdr, int direction) {
    struct tuple4 addr;
    switch(direction) {
		case NIDS_TCP_RESUME_CLIENT:
			addr.source = ntohs(this_tcphdr->th_sport);
			addr.dest = ntohs(this_tcphdr->th_dport);
			addr.saddr = this_iphdr->ip_src.s_addr;
			addr.daddr = this_iphdr->ip_dst.s_addr;
			break;
		case NIDS_TCP_RESUME_SERVER:
			addr.source = ntohs(this_tcphdr->th_dport);
			addr.dest = ntohs(this_tcphdr->th_sport);
			addr.saddr = this_iphdr->ip_dst.s_addr;
			addr.daddr = this_iphdr->ip_src.s_addr;
			break;
		default:
			return NULL;
    }
    struct tcp_stream *a_tcp = conn_pool_add(&addr);
    a_tcp->addr = addr;
	struct half_stream *half;
	struct half_stream *other_half;
	if (direction == NIDS_TCP_RESUME_CLIENT) {
		half = &a_tcp->client;
		other_half = &a_tcp->server;
	}
	else {
		half = &a_tcp->server;
		other_half = &a_tcp->client;
	}
	half->state = TCP_ESTABLISHED;
	half->seq = ntohl(this_tcphdr->th_seq) + 1;
	half->first_data_seq = half->seq - 1;
	half->window = ntohs(this_tcphdr->th_win);
	half->ts_on = 0;
	half->wscale = nids_params.tcp_resume_wscale;
	if (this_tcphdr->th_flags & TH_ACK) {
		half->ack_seq = ntohl(this_tcphdr->th_ack); 
    }
	other_half->ack_seq = half->seq;
	other_half->state = TCP_ESTABLISHED;
	other_half->resume_second_half = 1;
	other_half->ts_on = 0;
	other_half->window = half->window;
	other_half->wscale = nids_params.tcp_resume_wscale;

    return a_tcp;
}
#endif


/* static void */
/* add_new_tcp(struct tcphdr * this_tcphdr, struct ip * this_iphdr) */
/* { */
/*   struct tcp_stream *tolink; */
/*   struct tcp_stream *a_tcp; */
/*   int hash_index; */
/*   struct tuple4 addr; */
  
/*   addr.source = ntohs(this_tcphdr->th_sport); */
/*   addr.dest = ntohs(this_tcphdr->th_dport); */
/*   addr.saddr = this_iphdr->ip_src.s_addr; */
/*   addr.daddr = this_iphdr->ip_dst.s_addr; */
/*   hash_index = mk_hash_index(addr); */
  
/*   if (tcp_num > max_stream) { */
/*     struct lurker_node *i; */
/*     int orig_client_state=tcp_oldest->client.state; */
/*     tcp_oldest->nids_state = NIDS_TIMED_OUT; */
/*     for (i = tcp_oldest->listeners; i; i = i->next) */
/*       (i->item) (tcp_oldest, &i->data); */
/*     nids_free_tcp_stream(tcp_oldest); */
/*     if (orig_client_state!=TCP_SYN_SENT) */
/*       nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_TOOMUCH, ugly_iphdr, this_tcphdr); */
/*   } */
/*   a_tcp = free_streams; */
/*   if (!a_tcp) { */
/*     fprintf(stderr, "gdb me ...\n"); */
/*     pause(); */
/*   } */
/*   free_streams = a_tcp->next_free; */
  
/*   tcp_num++; */
/*   tolink = tcp_stream_table[hash_index]; */
/*   memset(a_tcp, 0, sizeof(struct tcp_stream)); */
/*   a_tcp->hash_index = hash_index; */
/*   a_tcp->addr = addr; */
/*   a_tcp->client.state = TCP_SYN_SENT; */
/*   a_tcp->client.seq = ntohl(this_tcphdr->th_seq) + 1; */
/*   a_tcp->client.first_data_seq = a_tcp->client.seq; */
/*   a_tcp->client.window = ntohs(this_tcphdr->th_win); */
/*   a_tcp->client.ts_on = get_ts(this_tcphdr, &a_tcp->client.curr_ts); */
/*   a_tcp->client.wscale_on = get_wscale(this_tcphdr, &a_tcp->client.wscale); */
/*   a_tcp->server.state = TCP_CLOSE; */
/*   a_tcp->next_node = tolink; */
/*   a_tcp->prev_node = 0; */
/*   if (tolink) */
/*     tolink->prev_node = a_tcp; */
/*   tcp_stream_table[hash_index] = a_tcp; */
/*   a_tcp->next_time = tcp_latest; */
/*   a_tcp->prev_time = 0; */
/*   if (!tcp_oldest) */
/*     tcp_oldest = a_tcp; */
/*   if (tcp_latest) */
/*     tcp_latest->prev_time = a_tcp; */
/*   tcp_latest = a_tcp; */
/* } */

static void 
conn_pool_add_new_tcp(struct tcphdr * this_tcphdr, struct ip * this_iphdr) {
    struct tuple4 addr_tuple4;
    addr_tuple4.source = ntohs(this_tcphdr->th_sport);
    addr_tuple4.dest = ntohs(this_tcphdr->th_dport);
    addr_tuple4.saddr = this_iphdr->ip_src.s_addr;
    addr_tuple4.daddr = this_iphdr->ip_dst.s_addr;
    struct tcp_stream *a_tcp = conn_pool_add(&addr_tuple4);

    // init a_tcp
    a_tcp->addr = addr_tuple4;
    a_tcp->client.state = TCP_SYN_SENT;
    a_tcp->client.seq = ntohl(this_tcphdr->th_seq) + 1; // use to check ACK packet sequence
    a_tcp->client.first_data_seq = a_tcp->client.seq;
    a_tcp->client.window = ntohs(this_tcphdr->th_win);
    a_tcp->client.ts_on = get_ts(this_tcphdr, &a_tcp->client.curr_ts);
    a_tcp->client.wscale_on = get_wscale(this_tcphdr, &a_tcp->client.wscale);
    a_tcp->server.state = TCP_CLOSE;
}

static void
add2buf(struct half_stream * rcv, char *data, int datalen)
{
  int toalloc;
  
  //if (datalen + rcv->count - rcv->offset > rcv->bufsize) {
  if (datalen + rcv->data_count - rcv->offset > rcv->bufsize) {
    if (!rcv->data) {
      if (datalen < 2048)
	toalloc = 4096;
      else
	toalloc = datalen * 2;
      rcv->data = malloc(toalloc);
      rcv->bufsize = toalloc;
    }
    else {
      if (datalen < rcv->bufsize)
      	toalloc = 2 * rcv->bufsize;
      else	
      	toalloc = rcv->bufsize + 2*datalen;
      rcv->data = realloc(rcv->data, toalloc);
      rcv->bufsize = toalloc;
    }
    if (!rcv->data)
      nids_params.no_mem("add2buf");
  }
  // memcpy(rcv->data + rcv->count - rcv->offset, data, datalen);
  memcpy(rcv->data + rcv->data_count - rcv->offset, data, datalen);
  rcv->count_new = datalen;
  rcv->count += datalen;
  rcv->data_count += datalen;
}

static void
ride_lurkers(struct tcp_stream * a_tcp, char mask)
{
  struct lurker_node *i;
  char cc, sc, ccu, scu;
  
  for (i = a_tcp->listeners; i; i = i->next)
    if (i->whatto & mask) {
      cc = a_tcp->client.collect;
      sc = a_tcp->server.collect;
      ccu = a_tcp->client.collect_urg;
      scu = a_tcp->server.collect_urg;

      (i->item) (a_tcp, &i->data);
      if (cc < a_tcp->client.collect)
	i->whatto |= COLLECT_cc;
      if (ccu < a_tcp->client.collect_urg)
	i->whatto |= COLLECT_ccu;
      if (sc < a_tcp->server.collect)
	i->whatto |= COLLECT_sc;
      if (scu < a_tcp->server.collect_urg)
	i->whatto |= COLLECT_scu;
      if (cc > a_tcp->client.collect)
	i->whatto &= ~COLLECT_cc;
      if (ccu > a_tcp->client.collect_urg)
	i->whatto &= ~COLLECT_ccu;
      if (sc > a_tcp->server.collect)
	i->whatto &= ~COLLECT_sc;
      if (scu > a_tcp->server.collect_urg)
	i->whatto &= ~COLLECT_scu;
    }
}

static void
notify(struct tcp_stream * a_tcp, struct half_stream * rcv)
{
  struct lurker_node *i, **prev_addr;
  char mask;

  if (rcv->count_new_urg) {
    if (!rcv->collect_urg)
      return;
    if (rcv == &a_tcp->client)
      mask = COLLECT_ccu;
    else
      mask = COLLECT_scu;
    ride_lurkers(a_tcp, mask);
    goto prune_listeners;
  }
  if (rcv->collect) {
    if (rcv == &a_tcp->client)
      mask = COLLECT_cc;
    else
      mask = COLLECT_sc;
   do {
	int total;
		// a_tcp->read = rcv->count - rcv->offset;
        a_tcp->read = rcv->data_count - rcv->offset;
		  total=a_tcp->read;
  
	    ride_lurkers(a_tcp, mask);
	    if (a_tcp->read>total-rcv->count_new)
	    	rcv->count_new=total-a_tcp->read;
	    
	    if (a_tcp->read > 0) {
	      // memmove(rcv->data, rcv->data + a_tcp->read, rcv->count - rcv->offset - a_tcp->read);
	      memmove(rcv->data, rcv->data + a_tcp->read, rcv->data_count - rcv->offset - a_tcp->read);
	      rcv->offset += a_tcp->read;
	    }
	}while (nids_params.one_loop_less && a_tcp->read>0 && rcv->count_new); 
// we know that if one_loop_less!=0, we have only one callback to notify
   rcv->count_new=0;
   if (rcv->data_count == rcv->offset) {
       rcv->data_count = rcv->offset = 0;
   }
  }
 prune_listeners:
  prev_addr = &a_tcp->listeners;
  i = a_tcp->listeners;
  while (i)
    if (!i->whatto) {
      *prev_addr = i->next;
      free(i);
      i = *prev_addr;
    }
    else {
      prev_addr = &i->next;
      i = i->next;
    }
}

static void
add_from_skb(struct tcp_stream * a_tcp, struct half_stream * rcv,
	     struct half_stream * snd,
	     u_char *data, int datalen,
	     u_int this_seq, char fin, char urg, u_int urg_ptr)
{
  u_int lost = EXP_SEQ - this_seq;
  int to_copy, to_copy2;
  
  if (urg && after(urg_ptr, EXP_SEQ - 1) &&
      (!rcv->urg_seen || after(urg_ptr, rcv->urg_ptr))) {
    rcv->urg_ptr = urg_ptr;
    rcv->urg_seen = 1;
  }
  if (rcv->urg_seen && after(rcv->urg_ptr + 1, this_seq + lost) &&
      before(rcv->urg_ptr, this_seq + datalen)) {
    to_copy = rcv->urg_ptr - (this_seq + lost);
    if (to_copy > 0) {
      if (rcv->collect) {
	add2buf(rcv, (char *)(data + lost), to_copy);
	notify(a_tcp, rcv);
      }
      else {
	rcv->count += to_copy;
    rcv->data_count += to_copy;
	//rcv->offset = rcv->count; /* clear the buffer */
    rcv->offset = rcv->data_count;
      }
    }
    rcv->urgdata = data[rcv->urg_ptr - this_seq];
    rcv->count_new_urg = 1;
    notify(a_tcp, rcv);
    rcv->count_new_urg = 0;
    rcv->urg_seen = 0;
    rcv->urg_count++;
    to_copy2 = this_seq + datalen - rcv->urg_ptr - 1;
    if (to_copy2 > 0) {
      if (rcv->collect) {
	add2buf(rcv, (char *)(data + lost + to_copy + 1), to_copy2);
	notify(a_tcp, rcv);
      }
      else {
	rcv->count += to_copy2;
    rcv->data_count += to_copy2;
	// rcv->offset = rcv->count; /* clear the buffer */
    rcv->offset = rcv->data_count;
      }
    }
  }
  else {
    if (datalen - lost > 0) {
      if (rcv->collect) {
	add2buf(rcv, (char *)(data + lost), datalen - lost);
	notify(a_tcp, rcv);
      }
      else {
	rcv->count += datalen - lost;
    rcv->data_count += datalen - lost;
	//rcv->offset = rcv->count; /* clear the buffer */
    rcv->offset = rcv->data_count;
      }
    }
  }
  if (fin) {
    snd->state = FIN_SENT;
    if (rcv->state == TCP_CLOSING)
      add_tcp_closing_timeout(a_tcp);
  }
}

static void
tcp_queue(struct tcp_stream * a_tcp, struct tcphdr * this_tcphdr,
	  struct half_stream * snd, struct half_stream * rcv,
	  char *data, int datalen, int skblen
	  )
{
  u_int this_seq = ntohl(this_tcphdr->th_seq);
  struct skbuff *pakiet, *tmp;
  
  /*
   * Did we get anything new to ack?
   */
  
  if (!after(this_seq, EXP_SEQ)) {
    if (after(this_seq + datalen + (this_tcphdr->th_flags & TH_FIN), EXP_SEQ)) {
      /* the packet straddles our window end */
      get_ts(this_tcphdr, &snd->curr_ts);
      add_from_skb(a_tcp, rcv, snd, (u_char *)data, datalen, this_seq,
		   (this_tcphdr->th_flags & TH_FIN),
		   (this_tcphdr->th_flags & TH_URG),
		   ntohs(this_tcphdr->th_urp) + this_seq - 1);
      /*
       * Do we have any old packets to ack that the above
       * made visible? (Go forward from skb)
       */
      pakiet = rcv->list;
      while (pakiet) {
	if (after(pakiet->seq, EXP_SEQ))
	  break;
	if (after(pakiet->seq + pakiet->len + pakiet->fin, EXP_SEQ)) {
	  add_from_skb(a_tcp, rcv, snd, pakiet->data,
		       pakiet->len, pakiet->seq, pakiet->fin, pakiet->urg,
		       pakiet->urg_ptr + pakiet->seq - 1);
        }
	rcv->rmem_alloc -= pakiet->truesize;
	if (pakiet->prev)
	  pakiet->prev->next = pakiet->next;
	else
	  rcv->list = pakiet->next;
	if (pakiet->next)
	  pakiet->next->prev = pakiet->prev;
	else
	  rcv->listtail = pakiet->prev;
	tmp = pakiet->next;
	free(pakiet->data);
	free(pakiet);
	pakiet = tmp;
      }
    }
    else
      return;
  }
  else {
    struct skbuff *p = rcv->listtail;

    pakiet = mknew(struct skbuff);
    pakiet->truesize = skblen;
    rcv->rmem_alloc += pakiet->truesize;
    pakiet->len = datalen;
    pakiet->data = malloc(datalen);
    if (!pakiet->data)
      nids_params.no_mem("tcp_queue");
    memcpy(pakiet->data, data, datalen);
    pakiet->fin = (this_tcphdr->th_flags & TH_FIN);
    /* Some Cisco - at least - hardware accept to close a TCP connection
     * even though packets were lost before the first TCP FIN packet and
     * never retransmitted; this violates RFC 793, but since it really
     * happens, it has to be dealt with... The idea is to introduce a 10s
     * timeout after TCP FIN packets were sent by both sides so that
     * corresponding libnids resources can be released instead of waiting
     * for retransmissions which will never happen.  -- Sebastien Raveau
     */
    if (pakiet->fin) {
      snd->state = TCP_CLOSING;
      if (rcv->state == FIN_SENT || rcv->state == FIN_CONFIRMED)
	add_tcp_closing_timeout(a_tcp);
    }
    pakiet->seq = this_seq;
    pakiet->urg = (this_tcphdr->th_flags & TH_URG);
    pakiet->urg_ptr = ntohs(this_tcphdr->th_urp);
    for (;;) {
      if (!p || !after(p->seq, this_seq))
	break;
      p = p->prev;
    }
    if (!p) {
      pakiet->prev = 0;
      pakiet->next = rcv->list;
      if (rcv->list)
         rcv->list->prev = pakiet;
      rcv->list = pakiet;
      if (!rcv->listtail)
	rcv->listtail = pakiet;
    }
    else {
      pakiet->next = p->next;
      p->next = pakiet;
      pakiet->prev = p;
      if (pakiet->next)
	pakiet->next->prev = pakiet;
      else
	rcv->listtail = pakiet;
    }
  }
}

static void
prune_queue(struct half_stream * rcv, struct tcphdr * this_tcphdr)
{
  struct skbuff *tmp, *p = rcv->list;

  nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_BIGQUEUE, ugly_iphdr, this_tcphdr);
  while (p) {
    free(p->data);
    tmp = p->next;
    free(p);
    p = tmp;
  }
  rcv->list = rcv->listtail = 0;
  rcv->rmem_alloc = 0;
}

static void
handle_ack(struct half_stream * snd, u_int acknum)
{
  int ackdiff;

  ackdiff = acknum - snd->ack_seq;
  if (ackdiff > 0) {
    snd->ack_seq = acknum;
  }
}
#if 0
static void
check_flags(struct ip * iph, struct tcphdr * th)
{
  u_char flag = *(((u_char *) th) + 13);
  if (flag & 0x40 || flag & 0x80)
    nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_BADFLAGS, iph, th);
//ECN is really the only cause of these warnings...
}
#endif

struct tcp_stream *
find_stream(struct tcphdr * this_tcphdr, struct ip * this_iphdr,
	    int *from_client)
{
  struct tuple4 this_addr, reversed;
  struct tcp_stream *a_tcp;

  this_addr.source = ntohs(this_tcphdr->th_sport);
  this_addr.dest = ntohs(this_tcphdr->th_dport);
  this_addr.saddr = this_iphdr->ip_src.s_addr;
  this_addr.daddr = this_iphdr->ip_dst.s_addr;
  // a_tcp = nids_find_tcp_stream(&this_addr);
  a_tcp = conn_pool_find(&this_addr);
  if (a_tcp) {
    *from_client = 1;
    return a_tcp;
  }
  reversed.source = ntohs(this_tcphdr->th_dport);
  reversed.dest = ntohs(this_tcphdr->th_sport);
  reversed.saddr = this_iphdr->ip_dst.s_addr;
  reversed.daddr = this_iphdr->ip_src.s_addr;
  // a_tcp = nids_find_tcp_stream(&reversed);
  a_tcp = conn_pool_find(&reversed);
  if (a_tcp) {
    *from_client = 0;
    return a_tcp;
  }
  return 0;
}

/* struct tcp_stream * */
/* nids_find_tcp_stream(struct tuple4 *addr) */
/* { */
/*   int hash_index; */
/*   struct tcp_stream *a_tcp; */

/*   hash_index = mk_hash_index(*addr); */
/*   for (a_tcp = tcp_stream_table[hash_index]; */
/*        a_tcp && memcmp(&a_tcp->addr, addr, sizeof (struct tuple4)); */
/*        a_tcp = a_tcp->next_node); */
/*   return a_tcp ? a_tcp : 0; */
/* } */


void tcp_exit(void)
{
  /* int i; */
  /* struct lurker_node *j; */
  /* struct tcp_stream *a_tcp, *t_tcp; */

  /* if (!tcp_stream_table || !streams_pool) */
  /*   return; */
  /* for (i = 0; i < tcp_stream_table_size; i++) { */
  /*   a_tcp = tcp_stream_table[i]; */
  /*   while(a_tcp) { */
  /*     t_tcp = a_tcp; */
  /*     a_tcp = a_tcp->next_node; */
  /*     for (j = t_tcp->listeners; j; j = j->next) { */
  /*         t_tcp->nids_state = NIDS_EXITING; */
	  /* (j->item)(t_tcp, &j->data); */
  /*     } */
  /*     nids_free_tcp_stream(t_tcp); */
  /*   } */
  /* } */
  /* free(tcp_stream_table); */
  /* tcp_stream_table = NULL; */
  /* free(streams_pool); */
  /* streams_pool = NULL; */
  /* /1* FIXME: anything else we should free? *1/ */
  /* /1* yes plz.. *1/ */
  /* tcp_latest = tcp_oldest = NULL; */
  /* tcp_num = 0; */
    conn_pool_destroy();
}

void
process_tcp(u_char * data, int skblen)
{
  struct ip *this_iphdr = (struct ip *)data;
  struct tcphdr *this_tcphdr = (struct tcphdr *)(data + 4 * this_iphdr->ip_hl);
  int datalen, iplen;
  int from_client = 1;
  unsigned int tmp_ts;
  struct tcp_stream *a_tcp;
  struct half_stream *snd, *rcv;
#ifdef ENABLE_TCPREASM
  int resumed_tcp = 0;
#endif

  ugly_iphdr = this_iphdr;
  iplen = ntohs(this_iphdr->ip_len);
  if ((unsigned)iplen < 4 * this_iphdr->ip_hl + sizeof(struct tcphdr)) {
    nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_HDR, this_iphdr,
		       this_tcphdr);
    return;
  } // ktos sie bawi
  
  datalen = iplen - 4 * this_iphdr->ip_hl - 4 * this_tcphdr->th_off;
  
  if (datalen < 0) {
    nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_HDR, this_iphdr,
		       this_tcphdr);
    return;
  } // ktos sie bawi

  if ((this_iphdr->ip_src.s_addr | this_iphdr->ip_dst.s_addr) == 0) {
    nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_HDR, this_iphdr,
		       this_tcphdr);
    return;
  }
  if (!(this_tcphdr->th_flags & TH_ACK))
    detect_scan(this_iphdr);
  if (!nids_params.n_tcp_streams) return;
  if (my_tcp_check(this_tcphdr, iplen - 4 * this_iphdr->ip_hl,
		   this_iphdr->ip_src.s_addr, this_iphdr->ip_dst.s_addr)) {
    nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_HDR, this_iphdr,
		       this_tcphdr);
    return;
  }
	
#ifdef ENABLE_TCPREASM_DEBUG
  DEBUG_REASSEMBLY("process_tcp starting: packet is %u.%u.%u.%u:%u->%u.%u.%u.%u:%u\n",
     (ntohl(this_iphdr->ip_src.s_addr) >> 24) & 0xff,
     (ntohl(this_iphdr->ip_src.s_addr) >> 16) & 0xff,
     (ntohl(this_iphdr->ip_src.s_addr) >> 8) & 0xff,
     (ntohl(this_iphdr->ip_src.s_addr)) & 0xff,
     ntohs(this_tcphdr->th_sport),
     (ntohl(this_iphdr->ip_dst.s_addr) >> 24) & 0xff,
     (ntohl(this_iphdr->ip_dst.s_addr) >> 16) & 0xff,
     (ntohl(this_iphdr->ip_dst.s_addr) >> 8) & 0xff,
     (ntohl(this_iphdr->ip_dst.s_addr)) & 0xff,
     ntohs(this_tcphdr->th_dport)    
  );
#endif
	
#if 0
  check_flags(this_iphdr, this_tcphdr);
//ECN
#endif
  if (!(a_tcp = find_stream(this_tcphdr, this_iphdr, &from_client))) {
    if ((this_tcphdr->th_flags & TH_SYN) &&
	!(this_tcphdr->th_flags & TH_ACK) &&
	!(this_tcphdr->th_flags & TH_RST)) {
      // add_new_tcp(this_tcphdr, this_iphdr);
      conn_pool_add_new_tcp(this_tcphdr, this_iphdr);
      return;
    }
		
#ifdef ENABLE_TCPREASM
#ifdef ENABLE_TCPREASM_DEBUG
    DEBUG_REASSEMBLY("packet is not in stream context: SYN %u, RST %u, ACK %u\n",
      this_tcphdr->th_flags & TH_SYN,
      this_tcphdr->th_flags & TH_RST,
      this_tcphdr->th_flags & TH_ACK
    );
#endif
		
    /* does this look like a stream we should try to resume?
     * the conditions for it are:
     * - No SYN (that's the whole point)
     * - No RST or FIN (no point in doing that)
     * - we have a resume callback to identify direction
     */
    if ((this_tcphdr->th_flags & TH_SYN) != 0 ||
	(this_tcphdr->th_flags & TH_RST) != 0 ||
	(this_tcphdr->th_flags & TH_FIN) != 0) {
	    return;
    }
    else {
      struct proc_node *i;
      for (i = tcp_resume_procs; i; i = i->next) {
        int resume;

#ifdef ENABLE_TCPREASM_DEBUG
	DEBUG_REASSEMBLY("trying to resume stream\n");
#endif
				
	i->item(this_tcphdr, this_iphdr, &resume);
	from_client = (resume == NIDS_TCP_RESUME_CLIENT);
	a_tcp = initiate_tcp_resume(this_tcphdr, this_iphdr, resume);
				
#ifdef ENABLE_TCPREASM_DEBUG
        DEBUG_REASSEMBLY("a_tcp = %p, from_client = %u, resume = %u\n",
	   a_tcp, from_client, resume);
#endif
				
	if (a_tcp) {
	  resumed_tcp = 1;
	  break;
	}
      }
    }

    if (!resumed_tcp)
      return;
  }
#else
    return;
  }
#endif

  if (from_client) {
    snd = &a_tcp->client;
    rcv = &a_tcp->server;
  }
  else {
    rcv = &a_tcp->client;
    snd = &a_tcp->server;
  }
		
#ifdef ENABLE_TCPREASM
#ifdef ENABLE_TCPREASM_DEBUG
  DEBUG_REASSEMBLY("processing packet: seq = %u, ack = %u, snd->seq = %u, rcv->ack_seq = %u\n",
      ntohl(this_tcphdr->th_seq), 
      ntohl(this_tcphdr->th_ack),
      snd->seq, rcv->ack_seq);
#endif
		
  /* are we the 2nd half of the resume? */
  if (snd->resume_second_half) {
	snd->seq = ntohl(this_tcphdr->th_seq) + 1;
	snd->first_data_seq = snd->seq - 1;
	snd->window = ntohs(this_tcphdr->th_win);
	snd->resume_second_half = 0;
	snd->ack_seq = rcv->seq;
			
#ifdef ENABLE_TCPREASM_DEBUG
	DEBUG_REASSEMBLY("second half resumed, seq = %u, first = %u, ack = %u\n",
	 snd->seq, snd->first_data_seq, snd->ack_seq);
#endif
			
  }
		
  if (resumed_tcp) {
	snd->state = TCP_ESTABLISHED;
	a_tcp->nids_state = NIDS_RESUME;
	goto do_lurkers;
  }
#endif
		
  /* normal SYN+ACK processing */
  if ((this_tcphdr->th_flags & TH_SYN)) {
    if (from_client || a_tcp->client.state != TCP_SYN_SENT ||
      a_tcp->server.state != TCP_CLOSE || !(this_tcphdr->th_flags & TH_ACK))
      return;
    if (a_tcp->client.seq != ntohl(this_tcphdr->th_ack))
      return;
    a_tcp->server.state = TCP_SYN_RECV;
    a_tcp->server.seq = ntohl(this_tcphdr->th_seq) + 1;
    a_tcp->server.first_data_seq = a_tcp->server.seq;
    a_tcp->server.ack_seq = ntohl(this_tcphdr->th_ack);
    a_tcp->server.window = ntohs(this_tcphdr->th_win);
    if (a_tcp->client.ts_on) {
    	a_tcp->server.ts_on = get_ts(this_tcphdr, &a_tcp->server.curr_ts);
	if (!a_tcp->server.ts_on)
		a_tcp->client.ts_on = 0;
    } else a_tcp->server.ts_on = 0;	
    if (a_tcp->client.wscale_on) {
    	a_tcp->server.wscale_on = get_wscale(this_tcphdr, &a_tcp->server.wscale);
	if (!a_tcp->server.wscale_on) {
		a_tcp->client.wscale_on = 0;
		a_tcp->client.wscale  = 1;
		a_tcp->server.wscale = 1;
	}	
    } else {
    	a_tcp->server.wscale_on = 0;	
    	a_tcp->server.wscale = 1;
    }	
    return;
  }
  if (
  	! (  !datalen && ntohl(this_tcphdr->th_seq) == rcv->ack_seq  )
  	&&
  	( !before(ntohl(this_tcphdr->th_seq), rcv->ack_seq + rcv->window*rcv->wscale) ||
          before(ntohl(this_tcphdr->th_seq) + datalen, rcv->ack_seq)  
        )
     ) {
#ifdef ENABLE_TCPREASM_DEBUG
 	DEBUG_REASSEMBLY("packet is ignored: "
 	 "datalen=%u, seq=%u, rcv->ack_seq=%u, rcv->window=%u, rcv->wscale=%u\n",
 	 datalen, ntohl(this_tcphdr->th_seq),
 	 rcv->ack_seq, rcv->window, rcv->wscale);
#endif
     return;
     }

  if ((this_tcphdr->th_flags & TH_RST)) {
    if (a_tcp->nids_state == NIDS_DATA) {
      struct lurker_node *i;

      a_tcp->nids_state = NIDS_RESET;
      for (i = a_tcp->listeners; i; i = i->next)
	(i->item) (a_tcp, &i->data);
    }
    nids_free_tcp_stream(a_tcp);
    return;
  }

  /* PAWS check */
  if (rcv->ts_on && get_ts(this_tcphdr, &tmp_ts) && 
  	before(tmp_ts, snd->curr_ts))
  return; 	
  
  if ((this_tcphdr->th_flags & TH_ACK)) {
    if (from_client && a_tcp->client.state == TCP_SYN_SENT &&
	a_tcp->server.state == TCP_SYN_RECV) {
      if (ntohl(this_tcphdr->th_ack) == a_tcp->server.seq) {

	get_real_client_addr(a_tcp,this_tcphdr); 

    a_tcp->client.state = TCP_ESTABLISHED;
	a_tcp->client.ack_seq = ntohl(this_tcphdr->th_ack);
	{
	  struct proc_node *i;
	  struct lurker_node *j;
	  void *data;
	  
	  a_tcp->server.state = TCP_ESTABLISHED;
	  a_tcp->nids_state = NIDS_JUST_EST;
#ifdef ENABLE_TCPREASM
do_lurkers:	  
#ifdef ENABLE_TCPREASM_DEBUG
	  DEBUG_REASSEMBLY("notifying lurkers of new stream\n");
#endif
#endif
								
	  for (i = tcp_procs; i; i = i->next) {
	    char whatto = 0;
	    char cc = a_tcp->client.collect;
	    char sc = a_tcp->server.collect;
	    char ccu = a_tcp->client.collect_urg;
	    char scu = a_tcp->server.collect_urg;
	   
#ifdef ENABLE_TCPREASM_DEBUG
	    DEBUG_REASSEMBLY("  %p, nids_state = %u\n", i, a_tcp->nids_state);
#endif

	    (i->item) (a_tcp, &data);
	    if (cc < a_tcp->client.collect)
	      whatto |= COLLECT_cc;
	    if (ccu < a_tcp->client.collect_urg)
	      whatto |= COLLECT_ccu;
	    if (sc < a_tcp->server.collect)
	      whatto |= COLLECT_sc;
	    if (scu < a_tcp->server.collect_urg)
	      whatto |= COLLECT_scu;
	    if (nids_params.one_loop_less) {
	    		if (a_tcp->client.collect >=2) {
	    			a_tcp->client.collect=cc;
	    			whatto&=~COLLECT_cc;
	    		}
	    		if (a_tcp->server.collect >=2 ) {
	    			a_tcp->server.collect=sc;
	    			whatto&=~COLLECT_sc;
	    		}
	    }  
	    if (whatto) {
	      j = mknew(struct lurker_node);
	      j->item = i->item;
	      j->data = data;
	      j->whatto = whatto;
	      j->next = a_tcp->listeners;
	      a_tcp->listeners = j;
	    }
	  }
	  if (!a_tcp->listeners) {
#ifdef ENABLE_TCPREASM_DEBUG
	    DEBUG_REASSEMBLY("no listeners, killing stream\n");
#endif
									
	    nids_free_tcp_stream(a_tcp);
	    return;
	  }
	  a_tcp->nids_state = NIDS_DATA;
	}
      }
      // return;
    }
  }
  if ((this_tcphdr->th_flags & TH_ACK)) {
    handle_ack(snd, ntohl(this_tcphdr->th_ack));
    if (rcv->state == FIN_SENT)
      rcv->state = FIN_CONFIRMED;
    if (rcv->state == FIN_CONFIRMED && snd->state == FIN_CONFIRMED) {
      struct lurker_node *i;

      a_tcp->nids_state = NIDS_CLOSE;
      for (i = a_tcp->listeners; i; i = i->next)
	(i->item) (a_tcp, &i->data);
      nids_free_tcp_stream(a_tcp);
      return;
    }
  }
  if (datalen + (this_tcphdr->th_flags & TH_FIN) > 0)
     {
#ifdef ENABLE_TCPREASM_DEBUG
	DEBUG_REASSEMBLY("calling tcp_queue, datalen = %u, data = %.*s...\n", 
	 datalen, datalen > 10 ? 10 : datalen, (char *) (this_tcphdr) + 4 * this_tcphdr->th_off);
#endif

    tcp_queue(a_tcp, this_tcphdr, snd, rcv,
	      (char *) (this_tcphdr) + 4 * this_tcphdr->th_off,
	      datalen, skblen);
	}
  snd->window = ntohs(this_tcphdr->th_win);
  if (rcv->rmem_alloc > 65535)
    prune_queue(rcv, this_tcphdr);
  if (!a_tcp->listeners)
    nids_free_tcp_stream(a_tcp);
}

void
nids_discard(struct tcp_stream * a_tcp, int num)
{
  if (num < a_tcp->read)
    a_tcp->read = num;
}

void
nids_register_tcp(void (*x))
{
  register_callback(&tcp_procs, x);
}

void
nids_unregister_tcp(void (*x))
{
  unregister_callback(&tcp_procs, x);
}

#ifdef ENABLE_TCPREASM
void
nids_register_tcp_resume(void (*x))
{
  register_callback(&tcp_resume_procs, x);
}

void
nids_unregister_tcp_resume(void (*x))
{
  unregister_callback(&tcp_resume_procs, x);
}
#endif

/* int */
/* tcp_init(int size) */
/* { */
/*   int i; */
/*   struct tcp_timeout *tmp; */

/*   if (!size) return 0; */
/*   tcp_stream_table_size = size; */
/*   tcp_stream_table = calloc(tcp_stream_table_size, sizeof(char *)); */
/*   if (!tcp_stream_table) { */
/*     nids_params.no_mem("tcp_init"); */
/*     return -1; */
/*   } */
/*   max_stream = 3 * tcp_stream_table_size / 4; */
/*   streams_pool = (struct tcp_stream *) malloc((max_stream + 1) * sizeof(struct tcp_stream)); */
/*   if (!streams_pool) { */
/*     nids_params.no_mem("tcp_init"); */
/*     return -1; */
/*   } */
/*   for (i = 0; i < max_stream; i++) */
/*     streams_pool[i].next_free = &(streams_pool[i + 1]); */
/*   streams_pool[max_stream].next_free = 0; */
/*   free_streams = streams_pool; */
/*   init_hash(); */
/*   while (nids_tcp_timeouts) { */
/*     tmp = nids_tcp_timeouts->next; */
/*     free(nids_tcp_timeouts); */
/*     nids_tcp_timeouts = tmp; */
/*   } */
/*   return 0; */
/* } */

int tcp_init(int size) {
    int hash_bucket_size = 1000;
    conn_pool_init(hash_bucket_size, size);
    init_hash();

    struct tcp_timeout *tmp;
    while (nids_tcp_timeouts) {
        tmp = nids_tcp_timeouts->next;
        free(nids_tcp_timeouts);
        nids_tcp_timeouts = tmp;
    }
    return 0;
}

#if HAVE_ICMPHDR
#define STRUCT_ICMP struct icmphdr
#define ICMP_CODE   code
#define ICMP_TYPE   type
#else
#define STRUCT_ICMP struct icmp
#define ICMP_CODE   icmp_code
#define ICMP_TYPE   icmp_type
#endif

#ifndef ICMP_DEST_UNREACH
#define ICMP_DEST_UNREACH ICMP_UNREACH
#define ICMP_PROT_UNREACH ICMP_UNREACH_PROTOCOL
#define ICMP_PORT_UNREACH ICMP_UNREACH_PORT
#define NR_ICMP_UNREACH   ICMP_MAXTYPE
#endif				


void
process_icmp(u_char * data)
{
  struct ip *iph = (struct ip *) data;
  struct ip *orig_ip;
  STRUCT_ICMP *pkt;
  struct tcphdr *th;
  struct half_stream *hlf;
  int match_addr;
  struct tcp_stream *a_tcp;
  struct lurker_node *i;

  int from_client;
  /* we will use unsigned, to suppress warning; we must be careful with
     possible wrap when substracting 
     the following is ok, as the ip header has already been sanitized */
  unsigned int len = ntohs(iph->ip_len) - (iph->ip_hl << 2);
  
  if (len < sizeof(STRUCT_ICMP))
    return;
  pkt = (STRUCT_ICMP *) (data + (iph->ip_hl << 2));
  if (ip_compute_csum((char *) pkt, len))
    return;
  if (pkt->ICMP_TYPE != ICMP_DEST_UNREACH)
    return;
  /* ok due to check 7 lines above */  
  len -= sizeof(STRUCT_ICMP);
  // sizeof(struct icmp) is not what we want here
  
  if (len < sizeof(struct ip))
    return;

  orig_ip = (struct ip *) (((char *) pkt) + 8);
  if (len < (unsigned)(orig_ip->ip_hl << 2) + 8)
     return;
  /* subtraction ok due to the check above */
  len -= orig_ip->ip_hl << 2;
  if ((pkt->ICMP_CODE & 15) == ICMP_PROT_UNREACH ||
      (pkt->ICMP_CODE & 15) == ICMP_PORT_UNREACH)
    match_addr = 1;
  else
    match_addr = 0;
  if (pkt->ICMP_CODE > NR_ICMP_UNREACH)
    return;
  if (match_addr && (iph->ip_src.s_addr != orig_ip->ip_dst.s_addr))
    return;
  if (orig_ip->ip_p != IPPROTO_TCP)
    return;
  th = (struct tcphdr *) (((char *) orig_ip) + (orig_ip->ip_hl << 2));
  if (!(a_tcp = find_stream(th, orig_ip, &from_client)))
    return;
  if (a_tcp->addr.dest == iph->ip_dst.s_addr)
    hlf = &a_tcp->server;
  else
    hlf = &a_tcp->client;
  if (hlf->state != TCP_SYN_SENT && hlf->state != TCP_SYN_RECV)
    return;
  a_tcp->nids_state = NIDS_RESET;
  for (i = a_tcp->listeners; i; i = i->next)
    (i->item) (a_tcp, &i->data);
  nids_free_tcp_stream(a_tcp);
}
